package com.sojson.util.encryption;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.springframework.core.io.ClassPathResource;

import com.sojson.constant.Constant;
import com.sojson.util.encryption.enums.EEncryptionType;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;

/**
 * RSA加密解密工具类
 * 
 * @author liu
 * @date 2020-09-04
 */
public class RsaUtil {

    /** RSA最大加密明文大小 */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /** RSA最大解密密文大小 */
    @SuppressWarnings("unused")
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 获取商户私钥
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link PrivateKey} 商户私钥
     * @throws Exception 异常信息
     */
    public static PrivateKey getPrivateKey(String keyPath) throws Exception {
        ClassPathResource classPathResource = new ClassPathResource(keyPath);
        InputStream inputStream = null;
        String originalKey = null;
        try {
            inputStream = classPathResource.getInputStream();
            originalKey = IoUtil.read(inputStream, Constant.DEFAULT_ENCODING);
        } catch (Exception e) {
            if (inputStream != null) {
                inputStream.close();
            }
        }
        String privateKey = originalKey.replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");

        return loadPrivateKey(privateKey);
    }

    /**
     * 从字符串中加载私钥<br>
     * 加载时使用的是PKCS8EncodedKeySpec（PKCS#8编码的Key指令）。
     *
     * @param privateKeyStr 私钥
     * @return {@link PrivateKey}
     * @throws Exception 异常信息
     */
    public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64Util.decryptionToByte(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance(EEncryptionType.RSA.getType());
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 获取商户私钥字符串
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link String} 商户私钥
     * @throws Exception 异常信息
     */
    public static String getPrivateKeyStr(String keyPath) throws Exception {
        return getPrivateKeyStr(getPrivateKey(keyPath));
    }

    /**
     * 获取商户私钥字符串
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link String} 商户私钥
     * @throws Exception 异常信息
     */
    public static String getPrivateKeyStr(PrivateKey privateKey) {
        return Base64Util.encryption(privateKey.getEncoded());
    }

    /**
     * 获取证书
     *
     * @param certPath 平台证书路径
     * @return
     * @throws IOException 
     * @throws IORuntimeException 
     */
    public static X509Certificate getCertificate(String certPath) throws IOException {
        return getCertificate(FileUtil.getInputStream(certPath));
    }

    /**
     * 获取证书
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     * @throws IOException 
     */
    public static X509Certificate getCertificate(InputStream inputStream) throws IOException {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate)cf.generateCertificate(inputStream);
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 公钥加密
     * 
     * @param content       内容
     * @param certificate   平台公钥证书
     * @return
     * @throws Exception
     */
    public static String encryption(String content, X509Certificate certificate) throws Exception {
        return encryption(content.getBytes(Constant.DEFAULT_ENCODING), certificate);
    }

    /**
     * 公钥加密
     * 
     * @param content       内容
     * @param certificate   平台公钥证书
     * @return
     * @throws Exception
     */
    public static String encryption(byte[] content, X509Certificate certificate) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] cipherdata = cipher.doFinal(content);
            return Base64Util.encryption(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 私钥解密
     *
     * @param cipherText 加密字符
     * @param privateKey 私钥
     * @return 解密后的数据
     * @throws Exception 异常信息
     */
    public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] data = Base64Util.decryptionToByte(cipherText);
            return new String(cipher.doFinal(data), Constant.DEFAULT_ENCODING);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }

    /**
     * 公钥加密
     *
     * @param data      需要加密的数据
     * @param publicKey 公钥
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String encryptByPublicKey(String data, String publicKey) throws Exception {
        return encryptByPublicKey(data, publicKey, "RSA/ECB/PKCS1Padding");
    }

    /**
     * 公钥加密
     *
     * @param data      需要加密的数据
     * @param publicKey 公钥
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String encryptByPublicKeyByWx(String data, String publicKey) throws Exception {
        return encryptByPublicKey(data, publicKey, "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
    }

    /**
     * 公钥加密
     *
     * @param content   需要加密的数据
     * @param publicKey 公钥
     * @param fillMode  填充模式
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String encryptByPublicKey(String content, String publicKey, String fillMode) throws Exception {
        byte[] dataByte = content.getBytes(Constant.DEFAULT_ENCODING);
        byte[] keyBytes = Base64Util.decryptionToByte(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(EEncryptionType.RSA.getType());
        Key key = keyFactory.generatePublic(x509KeySpec);
        // 对数据加密
        Cipher cipher = Cipher.getInstance(fillMode);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        int inputLen = dataByte.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(dataByte, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return Base64Util.encryption(encryptedData);
    }

    /**
     * 公钥验证签名
     *
     * @param data      需要加密的数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 验证结果
     * @throws Exception 异常信息
     */
    public static boolean checkByPublicKey(String data, String sign, String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(EEncryptionType.RSA.getType());
        byte[] encodedKey = Base64Util.decryptionToByte(publicKey);
        PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
        java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
        signature.initVerify(pubKey);
        signature.update(data.getBytes(Constant.DEFAULT_ENCODING));
        return signature.verify(Base64Util.decryptionToByte(sign.getBytes(Constant.DEFAULT_ENCODING)));
    }

    /**
     * 公钥验证签名
     *
     * @param data      需要加密的数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 验证结果
     * @throws Exception 异常信息
     */
    public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
        java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes(Constant.DEFAULT_ENCODING));
        return signature.verify(Base64Util.decryptionToByte(sign.getBytes(Constant.DEFAULT_ENCODING)));
    }

}